﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Windows;
using System.Diagnostics;
using System.Windows.Media.Animation;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
using System.Xml;
using System.Globalization;
using System.Collections.ObjectModel;
using SkeletalAnimation3D.Core;
using System.Windows.Markup;
using System.ComponentModel;

namespace SkeletalAnimation3D
{
	public class BoneAnimationLibrary : INotifyPropertyChanged
	{
        private Random _randomizer = new Random();
        private Mesh _mesh = new Mesh();
        private Skeleton _skeleton = new Skeleton();
        private BoneAnimationDictionary _animations = new BoneAnimationDictionary();

        public BoneAnimationLibrary()
        {
        }

        public static BoneAnimationLibrary FromEmptyRoot()
        {
            BoneAnimationLibrary animationLibrary = new BoneAnimationLibrary();
            animationLibrary.Skeleton.Add(new Bone { Id = 0, Name = "Root", MeshPointIndices = null, ChildBones = new BoneCollection(), MeshPointWeights = new DoubleCollection(), Orientation = Quaternion.Identity, Position = new Point3D(0, 0, 0), Size = 2, Parent = null });
            return animationLibrary;
        }

        public BoneAnimationLibrary(Point3DCollection points, Vector3DCollection normals, PointCollection textureCoordinates) : this()
        {
            _mesh = new Mesh(points, normals, textureCoordinates);
            this.Skeleton.Add(new Bone { Id = 0, Name = "Root", MeshPointIndices = null, ChildBones = new BoneCollection(), MeshPointWeights = new DoubleCollection(), Orientation = Quaternion.Identity, Position = new Point3D(0, 0, 0), Size = 2, Parent = null });
        }
		public static BoneAnimationLibrary MakeTestLib()
		{
            BoneAnimationLibrary testLib = new BoneAnimationLibrary();
            string testAnimation = "simple test";
            testLib._animations = new BoneAnimationDictionary();
            testLib._mesh.Points = new Point3DCollection(new Point3D[] { new Point3D(-5, -5, 0), new Point3D(5, -5, 0), new Point3D(0, 5, 0), new Point3D(-5, 5, 0), new Point3D(5, 5, 0), new Point3D(0, 15, 0), new Point3D(-5, 15, 0), new Point3D(5, 15, 0), new Point3D(0, 25, 0) });
            testLib._mesh.Normals = new Vector3DCollection(new Vector3D[] { new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1), new Vector3D(0, 0, 1) });
            Bone root = new Bone{Id = 0, Name = "Root", Size = 2, Position = new Point3D(0, -5, 0), MeshPointIndices = null, Orientation = Quaternion.Identity, MeshPointWeights = null, Parent = null};
            Bone bone1 = new Bone{ Id = 1, Name = "Bone1", Size = 1, Position=new Point3D(0,5,0), MeshPointIndices = new Int32Collection(new int[]{3,4,5}),  MeshPointWeights = new DoubleCollection(new double[]{1,1,1}), Orientation = Quaternion.Identity, Parent = root};
            Bone bone2 = new Bone{Id = 2, Name = "Bone2", Size = 1, Position=new Point3D(0,15,0),  MeshPointIndices = new Int32Collection(new int[]{6,7,8}), MeshPointWeights = new DoubleCollection(new double[]{1,1,0.155}), Orientation = Quaternion.Identity, ChildBones = null, Parent = bone1};
            bone1.ChildBones.Add(bone2);
            root.ChildBones.Add(bone1);
            testLib.Skeleton.Root = root;
            testLib.Mesh.TextureCoordinates = new PointCollection();
            testLib.Mesh.TextureCoordinates.Add(new Point(0, 1));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.5, 1));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.25, 0.66));
            testLib.Mesh.TextureCoordinates.Add(new Point(0, 0.66));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.5, 0.66));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.25, 0.33));
            testLib.Mesh.TextureCoordinates.Add(new Point(0, 0.33));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.5, 0.33));
            testLib.Mesh.TextureCoordinates.Add(new Point(0.25, 0));
            BoneAnimation animation;
            animation = new BoneAnimation(testAnimation, new Duration(TimeSpan.FromSeconds(1.5)));
            testLib._animations.Add(animation);
            animation.Frames = new BoneFrameCollection();
            BoneFrame f;
            f = new BoneFrame {Offset = 0 };
            f.BoneOrientations = new BoneOrientationDictionary();
            f.BoneOrientations.Add(new BoneOrientation(0, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(1, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(2, Quaternion.Identity));
            animation.Frames.Add(f);
            f = new BoneFrame { Offset = 0.25 };
            f.BoneOrientations = new BoneOrientationDictionary();
            f.BoneOrientations.Add(new BoneOrientation(0, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(1, new Quaternion(new Vector3D(1, 0, 0), 90)));
            f.BoneOrientations.Add(new BoneOrientation(2, new Quaternion(new Vector3D(1, 0, 0), 180)));
            animation.Frames.Add(f);
            f = new BoneFrame { Offset = 0.50 };
            f.BoneOrientations = new BoneOrientationDictionary();
            f.BoneOrientations.Add(new BoneOrientation(0, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(1, new Quaternion(new Vector3D(1, 0, 0), 359)));
            f.BoneOrientations.Add(new BoneOrientation(2, new Quaternion(new Vector3D(1, 0, 0), 180)));
            animation.Frames.Add(f);
            f = new BoneFrame { Offset = 0.75 };
            f.BoneOrientations = new BoneOrientationDictionary();
            f.BoneOrientations.Add(new BoneOrientation(0, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(1, new Quaternion(new Vector3D(1, 0, 0), 90)));
            f.BoneOrientations.Add(new BoneOrientation(2, new Quaternion(new Vector3D(1, 0, 0), 180)));
            animation.Frames.Add(f);
            f = new BoneFrame { Offset = 1 };
            f.BoneOrientations = new BoneOrientationDictionary();
            f.BoneOrientations.Add(new BoneOrientation(0, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(1, Quaternion.Identity));
            f.BoneOrientations.Add(new BoneOrientation(2, Quaternion.Identity));
            animation.Frames.Add(f);

            return testLib;
		}
        
        public Mesh Mesh
        {
            get
            {
                return _mesh;
            }
            set
            {
                _mesh = value;
            }
        }
        public Skeleton Skeleton
        {
            get
            {
                return _skeleton;
            }
            set
            {
                if (_skeleton != value)
                {
                    _skeleton = value;
                    OnPropertyChanged("Skeleton");
                    
                }
            }
        }

        public BoneAnimationDictionary Animations
        {
            get { return _animations; }
            set { _animations = value; }
        }

        internal BoneAnimation this[string animationName]
        {
            get
            {
                if (String.IsNullOrEmpty(animationName))
                {
                    throw new ApplicationException("");
                }
                for (int i = 0; i < _animations.Count; i++)
                {
                    if (String.Compare(_animations[i].Name, animationName, true) == 0)
                    {
                        return _animations[i];
                    }
                }
                throw new ApplicationException(String.Format("Animation {0} does not exist.", animationName));
            }
        }

        public Int32Collection GetAttachedPoints(int boneId)
        {
            return _skeleton.GetAttachedPoints(boneId);
        }

        public void Save(Stream stream)
        {
            XamlWriter.Save(this, stream);
        }

        public static BoneAnimationLibrary Load(Stream stream)
        {
            BoneAnimationLibrary animationLibrary = (BoneAnimationLibrary)XamlReader.Load(stream);
            return animationLibrary;
        }

        internal SkeletonInfo GetSkeletonInfo()
        {
            SkeletonInfo skeleton = new SkeletonInfo();
            skeleton.Add(_skeleton.GetBoneInfo());
            return skeleton;
        }

        internal void CopySkeletonData(SkeletonInfo target)
        {
            _skeleton.CopyBoneData(target);
        }

        internal MeshInfo GetMeshInfo()
        {
            MeshInfo meshInfo = new MeshInfo();
            meshInfo.Points = _mesh.Points.Clone();
            meshInfo.Normals = _mesh.Normals.Clone();
            meshInfo.TextureCoordinates = _mesh.TextureCoordinates.Clone();
            return meshInfo;
        }

        internal void CopyMeshData(MeshInfo target)
        {
            for (int i = 0; i < _mesh.Points.Count; i++)
            {
                target.Points[i] = _mesh.Points[i];
            }
            for (int i = 0; i < _mesh.Normals.Count; i++)
            {
                target.Normals[i] = _mesh.Normals[i];
            }
            for (int i = 0; i < _mesh.TextureCoordinates.Count; i++)
            {
                target.TextureCoordinates[i] = _mesh.TextureCoordinates[i];
            }
        }

        public bool SetBoneSize(int boneId, double size)
        {
            if (_skeleton.SetBoneSize(boneId, size))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler p = PropertyChanged;
            if (p != null)
            {
                p(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        public bool SetBoneName(int boneId, string name)
        {
            if (_skeleton.SetBoneName(boneId, name))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        public bool MoveBone(int boneId, Vector3D offset)
        {
            if (_skeleton.MoveBone(boneId, offset))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        public bool AddBone(int parentBoneId, string name, Point3D position, double size)
        {
            int newId = _skeleton.GetNewBoneId();
            if (_skeleton.AddBone(parentBoneId, newId, name, position, size))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        public DoubleCollection GetPointWeights(int boneId)
        {
            return _skeleton.GetPointWeights(boneId);
        }

        public bool AttachPointsToBone(int boneId, Int32Collection pointIds)
        {
            if (_skeleton.AttachPointsToBone(boneId, pointIds))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        public bool DetachPointsFromBone(int boneId, Int32Collection pointIds)
        {
            if (_skeleton.DetachPointsFromBone(boneId, pointIds))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }

        public bool SetPointWeightsOnBone(int boneId, Int32Collection pointIds, double weight)
        {
            if (_skeleton.SetPointWeightsOnBone(boneId, pointIds, weight))
            {
                OnPropertyChanged("Skeleton");
                return true;
            }
            return false;
        }
    }
}
